// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/test/test_context_provider.h"

#include <stddef.h>
#include <stdint.h>

#include <set>
#include <vector>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "cc/output/context_cache_controller.h"
#include "cc/test/test_gles2_interface.h"
#include "cc/test/test_web_graphics_context_3d.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "third_party/skia/include/gpu/gl/GrGLInterface.h"

namespace cc {

// static
scoped_refptr<TestContextProvider> TestContextProvider::Create()
{
    return new TestContextProvider(base::MakeUnique<TestContextSupport>(),
        base::MakeUnique<TestGLES2Interface>(),
        TestWebGraphicsContext3D::Create());
}

// static
scoped_refptr<TestContextProvider> TestContextProvider::CreateWorker()
{
    scoped_refptr<TestContextProvider> worker_context_provider(
        new TestContextProvider(base::MakeUnique<TestContextSupport>(),
            base::MakeUnique<TestGLES2Interface>(),
            TestWebGraphicsContext3D::Create()));
    // Worker contexts are bound to the thread they are created on.
    if (!worker_context_provider->BindToCurrentThread())
        return nullptr;
    return worker_context_provider;
}

// static
scoped_refptr<TestContextProvider> TestContextProvider::Create(
    std::unique_ptr<TestWebGraphicsContext3D> context)
{
    DCHECK(context);
    return new TestContextProvider(base::MakeUnique<TestContextSupport>(),
        base::MakeUnique<TestGLES2Interface>(),
        std::move(context));
}

// static
scoped_refptr<TestContextProvider> TestContextProvider::Create(
    std::unique_ptr<TestGLES2Interface> gl)
{
    DCHECK(gl);
    return new TestContextProvider(base::MakeUnique<TestContextSupport>(),
        std::move(gl),
        TestWebGraphicsContext3D::Create());
}

// static
scoped_refptr<TestContextProvider> TestContextProvider::Create(
    std::unique_ptr<TestWebGraphicsContext3D> context,
    std::unique_ptr<TestContextSupport> support)
{
    DCHECK(context);
    DCHECK(support);
    return new TestContextProvider(std::move(support),
        base::MakeUnique<TestGLES2Interface>(),
        std::move(context));
}

TestContextProvider::TestContextProvider(
    std::unique_ptr<TestContextSupport> support,
    std::unique_ptr<TestGLES2Interface> gl,
    std::unique_ptr<TestWebGraphicsContext3D> context)
    : support_(std::move(support))
    , context3d_(std::move(context))
    , context_gl_(std::move(gl))
    , weak_ptr_factory_(this)
{
    DCHECK(main_thread_checker_.CalledOnValidThread());
    DCHECK(context3d_);
    DCHECK(context_gl_);
    context_thread_checker_.DetachFromThread();
    context_gl_->set_test_context(context3d_.get());
    context3d_->set_test_support(support_.get());
    // Just pass nullptr to the ContextCacheController for its task runner. Idle
    // handling is tested directly in ContextCacheController's unittests, and
    // isn't needed here.
    cache_controller_.reset(new ContextCacheController(support_.get(), nullptr));
}

TestContextProvider::~TestContextProvider()
{
    DCHECK(main_thread_checker_.CalledOnValidThread() || context_thread_checker_.CalledOnValidThread());
}

bool TestContextProvider::BindToCurrentThread()
{
    // This is called on the thread the context will be used.
    DCHECK(context_thread_checker_.CalledOnValidThread());

    if (bound_)
        return true;

    if (context_gl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR) {
        return false;
    }
    bound_ = true;

    context3d_->set_context_lost_callback(
        base::Bind(&TestContextProvider::OnLostContext,
            base::Unretained(this)));

    return true;
}

void TestContextProvider::DetachFromThread()
{
    context_thread_checker_.DetachFromThread();
}

gpu::Capabilities TestContextProvider::ContextCapabilities()
{
    DCHECK(bound_);
    DCHECK(context_thread_checker_.CalledOnValidThread());
    return context3d_->test_capabilities();
}

gpu::gles2::GLES2Interface* TestContextProvider::ContextGL()
{
    DCHECK(context3d_);
    DCHECK(bound_);
    DCHECK(context_thread_checker_.CalledOnValidThread());

    return context_gl_.get();
}

gpu::ContextSupport* TestContextProvider::ContextSupport()
{
    return support();
}

class GrContext* TestContextProvider::GrContext()
{
    DCHECK(bound_);
    DCHECK(context_thread_checker_.CalledOnValidThread());

    if (gr_context_)
        return gr_context_.get();

    sk_sp<const GrGLInterface> gl_interface(GrGLCreateNullInterface());
    gr_context_ = sk_sp<::GrContext>(GrContext::Create(
        kOpenGL_GrBackend,
        reinterpret_cast<GrBackendContext>(gl_interface.get())));

    cache_controller_->SetGrContext(gr_context_.get());

    // If GlContext is already lost, also abandon the new GrContext.
    if (ContextGL()->GetGraphicsResetStatusKHR() != GL_NO_ERROR)
        gr_context_->abandonContext();

    return gr_context_.get();
}

ContextCacheController* TestContextProvider::CacheController()
{
    DCHECK(context_thread_checker_.CalledOnValidThread());
    return cache_controller_.get();
}

void TestContextProvider::InvalidateGrContext(uint32_t state)
{
    DCHECK(bound_);
    DCHECK(context_thread_checker_.CalledOnValidThread());

    if (gr_context_)
        gr_context_.get()->resetContext(state);
}

base::Lock* TestContextProvider::GetLock()
{
    return &context_lock_;
}

void TestContextProvider::OnLostContext()
{
    DCHECK(context_thread_checker_.CalledOnValidThread());
    if (!lost_context_callback_.is_null())
        lost_context_callback_.Run();
    if (gr_context_)
        gr_context_->abandonContext();
}

TestWebGraphicsContext3D* TestContextProvider::TestContext3d()
{
    DCHECK(bound_);
    DCHECK(context_thread_checker_.CalledOnValidThread());

    return context3d_.get();
}

TestWebGraphicsContext3D* TestContextProvider::UnboundTestContext3d()
{
    return context3d_.get();
}

void TestContextProvider::SetLostContextCallback(
    const LostContextCallback& cb)
{
    DCHECK(context_thread_checker_.CalledOnValidThread());
    DCHECK(lost_context_callback_.is_null() || cb.is_null());
    lost_context_callback_ = cb;
}

} // namespace cc
